using System;
using System.Collections.Generic;
using System.Globalization;
using NFreeMarker.Template;
using NFreeMarker.Template.Utility;
using System.Linq;

namespace NFreeMarker.Core
{
    /**
     * A holder for builtins that operate exclusively on TemplateSequenceModels.
     */
    internal abstract class SequenceBuiltins
    {
        internal abstract class SequenceBuiltIn : BuiltIn
        {
            protected override ITemplateModel _getAsTemplateModel(Environment env)
            {
                ITemplateModel model = Target.GetAsTemplateModel(env);
                if (!(model is ITemplateSequenceModel))
                {
                    throw InvalidTypeException(model, Target, env, "sequence");
                }
                return CalculateResult((ITemplateSequenceModel) model);
            }

            protected abstract ITemplateModel CalculateResult(ITemplateSequenceModel tsm);
        }

        internal class FirstBuiltIn : SequenceBuiltIn
        {
            protected override ITemplateModel CalculateResult(ITemplateSequenceModel tsm)
            {
                if (tsm.Size() == 0)
                {
                    return null;
                }
                return tsm.Get(0);
            }
        }

        internal class LastBuiltIn : SequenceBuiltIn
        {
            protected override ITemplateModel CalculateResult(ITemplateSequenceModel tsm)
            {
                if (tsm.Size() == 0)
                {
                    return null;
                }
                return tsm.Get(tsm.Size() - 1);
            }
        }

        internal class ReverseBuiltIn : SequenceBuiltIn
        {
            protected override ITemplateModel CalculateResult(ITemplateSequenceModel tsm)
            {
                if (tsm is ReverseSequence)
                {
                    return ((ReverseSequence) tsm).Seq;
                }
                return new ReverseSequence(tsm);
            }

            private class ReverseSequence : ITemplateSequenceModel
            {
                internal readonly ITemplateSequenceModel Seq;

                internal ReverseSequence(ITemplateSequenceModel seq)
                {
                    Seq = seq;
                }

                public int Size()
                {
                    return Seq.Size();
                }

                public ITemplateModel Get(int index)
                {
                    return Seq.Get(Seq.Size() - 1 - index);
                }
            }
        }

        internal class SortBuiltIn : SequenceBuiltIn
        {
            private const int KeyTypeString = 1;
            private const int KeyTypeNumber = 2;
            private const int KeyTypeDate = 3;

            private Environment _env;

            protected override ITemplateModel _getAsTemplateModel(Environment env)
            {
                _env = env;
                return base._getAsTemplateModel(env);
            }

            protected override ITemplateModel CalculateResult(ITemplateSequenceModel seq)
            {
                return Sort(seq, null);
            }

            private static string StartErrorMessage(object keys)
            {
                return (keys == null ? "?sort" : "?sort_by(...)") + " failed: ";
            }

            /**
             * Sorts a sequence for the <tt>sort</tt> and <tt>sort_by</tt>
             * built-ins.
             * 
             * @param seq the sequence to sort.
             * @param keys the name of the subvariable whose value is used for the
             *     sorting. If the sorting is done by a sub-subvaruable, then this
             *     will be of length 2, and so on. If the sorting is done by the
             *     sequene items directly, then this argument has to be 0 length
             *     array or <code>null</code>.
             * @return a new sorted sequence, or the original sequence if the
             *     sequence length was 0.
             */

            internal ITemplateSequenceModel Sort(ITemplateSequenceModel seq, string[] keys)
            {
                int i;
                int keyCnt;

                int ln = seq.Size();
                if (ln == 0)
                {
                    return seq;
                }

                var res = new List<KeyValuePair>(ln);
                ITemplateModel item = seq.Get(0);
                if (keys != null)
                {
                    keyCnt = keys.Length;
                    if (keyCnt == 0)
                    {
                        keys = null;
                    }
                    else
                    {
                        for (i = 0; i < keyCnt; i++)
                        {
                            if (!(item is ITemplateHashModel))
                            {
                                throw new TemplateModelException(
                                    StartErrorMessage(keys)
                                    + (i == 0
                                           ? "You can't use ?sort_by when the "
                                             + "sequence items are not hashes."
                                           : "The subvariable "
                                             + StringUtil.JQuote(keys[i - 1])
                                             + " is not a hash, so ?sort_by "
                                             + "can't proceed by getting the "
                                             + StringUtil.JQuote(keys[i])
                                             + " subvariable."));
                            }

                            item = ((ITemplateHashModel) item).Get(keys[i]);
                            if (item == null)
                            {
                                throw new TemplateModelException(
                                    StartErrorMessage(keys)
                                    + "The " + StringUtil.JQuote(keys[i])
                                    + " subvariable "
                                    + (keyCnt == 1
                                           ? "was not found."
                                           : "(specified by ?sort_by argument number "
                                             + (i + 1) + ") was not found."));
                            }
                        }
                    }
                }
                else
                {
                    keyCnt = 0;
                }

                int keyType;
                if (item is ITemplateScalarModel)
                {
                    keyType = KeyTypeString;
                }
                else if (item is ITemplateNumberModel)
                {
                    keyType = KeyTypeNumber;
                }
                else if (item is ITemplateDateModel)
                {
                    keyType = KeyTypeDate;
                }
                else
                {
                    throw new TemplateModelException(
                        StartErrorMessage(keys)
                        + "Values used for sorting must be numbers, strings, or date/time values.");
                }

                if (keys == null)
                {
                    if (keyType == KeyTypeString)
                    {
                        for (i = 0; i < ln; i++)
                        {
                            item = seq.Get(i);
                            try
                            {
                                res.Add(new KeyValuePair(
                                            ((ITemplateScalarModel) item).GetAsString(),
                                            item));
                            }
                            catch (InvalidCastException)
                            {
                                if (!(item is ITemplateScalarModel))
                                {
                                    throw new TemplateModelException(
                                        StartErrorMessage(null)
                                        + "All values in the sequence must be "
                                        + "strings, because the first value "
                                        + "was a string. "
                                        + "The value at index " + i
                                        + " is not string.");
                                }
                                throw;
                            }
                        }
                    }
                    else if (keyType == KeyTypeNumber)
                    {
                        for (i = 0; i < ln; i++)
                        {
                            item = seq.Get(i);
                            try
                            {
                                res.Add(new KeyValuePair(
                                            ((ITemplateNumberModel)item).GetAsNumber(),
                                            item));
                            }
                            catch (InvalidCastException)
                            {
                                if (!(item is ITemplateNumberModel))
                                {
                                    throw new TemplateModelException(
                                        StartErrorMessage(null)
                                        + "All values in the sequence must be "
                                        + "numbers, because the first value "
                                        + "was a number. "
                                        + "The value at index " + i
                                        + " is not number.");
                                }
                                throw;
                            }
                        }
                    }
                    else if (keyType == KeyTypeDate)
                    {
                        for (i = 0; i < ln; i++)
                        {
                            item = seq.Get(i);
                            try
                            {
                                res.Add(new KeyValuePair(
                                            ((ITemplateDateModel) item).GetAsDate(),
                                            item));
                            }
                            catch (InvalidCastException)
                            {
                                if (!(item is ITemplateNumberModel))
                                {
                                    throw new TemplateModelException(
                                        StartErrorMessage(null)
                                        + "All values in the sequence must be "
                                        + "date/time values, because the first "
                                        + "value was a date/time. "
                                        + "The value at index " + i
                                        + " is not date/time.");
                                }
                                throw;
                            }
                        }
                    }
                    else
                    {
                        //throw new RuntimeException("FreeMarker bug: Bad key type");
                        throw new NotImplementedException();
                    }
                }
                else
                {
                    for (i = 0; i < ln; i++)
                    {
                        item = seq.Get(i);
                        object key = item;
                        for (int j = 0; j < keyCnt; j++)
                        {
                            try
                            {
                                key = ((ITemplateHashModel) key).Get(keys[j]);
                            }
                            catch (InvalidCastException)
                            {
                                if (!(key is ITemplateHashModel))
                                {
                                    throw new TemplateModelException(
                                        StartErrorMessage(keys)
                                        + "Problem with the sequence item at index " + i + ": "
                                        + "Can't get the " + StringUtil.JQuote(keys[j])
                                        + " subvariable, because the value is not a hash.");
                                }
                                throw;
                            }
                            if (key == null)
                            {
                                throw new TemplateModelException(
                                    StartErrorMessage(keys)
                                    + "Problem with the sequence item at index " + i + ": "
                                    + "The " + StringUtil.JQuote(keys[j])
                                    + " subvariable was not found.");
                            }
                        }
                        if (keyType == KeyTypeString)
                        {
                            try
                            {
                                res.Add(new KeyValuePair(
                                            ((ITemplateScalarModel) key).GetAsString(),
                                            item));
                            }
                            catch (InvalidCastException)
                            {
                                if (!(key is ITemplateScalarModel))
                                {
                                    throw new TemplateModelException(
                                        StartErrorMessage(keys)
                                        + "All key values in the sequence must be "
                                        + "date/time values, because the first key "
                                        + "value was a date/time. The key value at "
                                        + "index " + i + " is not a date/time.");
                                }
                                throw;
                            }
                        }
                        else if (keyType == KeyTypeNumber)
                        {
                            try
                            {
                                res.Add(new KeyValuePair(
                                            ((ITemplateNumberModel)key).GetAsNumber(),
                                            item));
                            }
                            catch (InvalidCastException)
                            {
                                if (!(key is ITemplateNumberModel))
                                {
                                    throw new TemplateModelException(
                                        StartErrorMessage(keys)
                                        + "All key values in the sequence must be "
                                        + "numbers, because the first key "
                                        + "value was a number. The key value at "
                                        + "index " + i + " is not a number.");
                                }
                            }
                        }
                        else if (keyType == KeyTypeDate)
                        {
                            try
                            {
                                res.Add(new KeyValuePair(
                                            ((ITemplateDateModel) key).GetAsDate(),
                                            item));
                            }
                            catch (InvalidCastException)
                            {
                                if (!(key is ITemplateDateModel))
                                {
                                    throw new TemplateModelException(
                                        StartErrorMessage(keys)
                                        + "All key values in the sequence must be "
                                        + "dates, because the first key "
                                        + "value was a date. The key value at "
                                        + "index " + i + " is not a date.");
                                }
                            }
                        }
                        else
                        {
                            //throw new RuntimeException("FreeMarker bug: Bad key type");
                            throw new NotImplementedException();
                        }
                    }
                }

                IComparer<KeyValuePair> cmprtr;
                if (keyType == KeyTypeString)
                {
                    cmprtr = new LexicalKeyValuePairComparator(_env);
                }
                else if (keyType == KeyTypeNumber)
                {
                    cmprtr = new NumericalKeyValuePairComparator(
                        Environment.GetCurrentEnvironment().GetArithmeticEngine());
                }
                else if (keyType == KeyTypeDate)
                {
                    cmprtr = new DateKeyValuePairComparator();
                }
                else
                {
                    //throw new RuntimeException("FreeMarker bug: Bad key type");
                    throw new NotImplementedException();
                }

                try
                {
                    res.Sort(cmprtr);
                }
                catch (InvalidCastException exc)
                {
                    // TODO: Will Sort throw InvalidCastException????
                    throw new TemplateModelException(
                        StartErrorMessage(keys)
                        + "Unexpected error while sorting:" + exc, exc);
                }

                return new TemplateModelListSequence(res.Select(kvp => kvp.Value).ToList());
            }

            private class KeyValuePair
            {
                internal KeyValuePair(object key, ITemplateModel value)
                {
                    Key = key;
                    Value = value;
                }

                internal readonly object Key;
                internal readonly ITemplateModel Value;
            }

            private class NumericalKeyValuePairComparator : IComparer<KeyValuePair>
            {
                private readonly ArithmeticEngine _ae;

                internal NumericalKeyValuePairComparator(ArithmeticEngine ae)
                {
                    _ae = ae;
                }

                public int Compare(KeyValuePair arg0, KeyValuePair arg1)
                {
                    try
                    {
                        return _ae.CompareNumbers(
                            (Number) arg0.Key,
                            (Number) arg1.Key);
                    }
                    catch (TemplateException e)
                    {
                        throw new InvalidCastException(
                            "Failed to compare numbers: " + e);
                    }
                }
            }

            private class LexicalKeyValuePairComparator : IComparer<KeyValuePair>
            {
                private readonly Environment _env;

                internal LexicalKeyValuePairComparator(Environment env)
                {
                    _env = env;
                }

                public int Compare(KeyValuePair arg0, KeyValuePair arg1)
                {
                    var key1 = (string) arg0.Key;
                    var key2 = (string) arg1.Key;
                    return string.Compare(key1, key2, _env.GetLocale(), CompareOptions.None);
                }
            }

            private class DateKeyValuePairComparator : IComparer<KeyValuePair> //, Serializable 
            {
                public int Compare(KeyValuePair arg0, KeyValuePair arg1)
                {
                    return ((DateTimeOffset) arg0.Key).CompareTo(
                        (DateTimeOffset) arg1.Key);
                }
            }
        }

        internal class SortByBuiltIn : SortBuiltIn
        {
            protected override ITemplateModel CalculateResult(ITemplateSequenceModel seq)
            {
                return new BuiltInMethod(this, seq);
            }

            private class BuiltInMethod : ITemplateMethodModelEx
            {
                private SortByBuiltIn _sortByBuiltIn;
                private readonly ITemplateSequenceModel _seq;

                internal BuiltInMethod(SortByBuiltIn sortByBuiltIn, ITemplateSequenceModel seq)
                {
                    _sortByBuiltIn = sortByBuiltIn;
                    _seq = seq;
                }

                public object Exec(IList<ITemplateModel> parameters)
                {
                    if (parameters.Count == 0)
                    {
                        throw new TemplateModelException(
                            "?sort_by(key) needs exactly 1 argument.");
                    }
                    string[] subvars;
                    ITemplateModel obj = parameters[0];
                    if (obj is ITemplateScalarModel)
                    {
                        subvars = new[] {((ITemplateScalarModel) obj).GetAsString()};
                    }
                    else if (obj is ITemplateSequenceModel)
                    {
                        var seq = (ITemplateSequenceModel) obj;
                        int ln = seq.Size();
                        subvars = new string[ln];
                        for (int i = 0; i < ln; i++)
                        {
                            ITemplateModel item = seq.Get(i);
                            try
                            {
                                subvars[i] = ((ITemplateScalarModel) item)
                                    .GetAsString();
                            }
                            catch (InvalidCastException)
                            {
                                if (!(item is ITemplateScalarModel))
                                {
                                    throw new TemplateModelException(
                                        "The argument to ?sort_by(key), when it "
                                        + "is a sequence, must be a sequence of "
                                        + "strings, but the item at index " + i
                                        + " is not a string.");
                                }
                            }
                        }
                    }
                    else
                    {
                        throw new TemplateModelException(
                            "The argument to ?sort_by(key) must be a string "
                            + "(the name of the subvariable), or a sequence of "
                            + "strings (the \"path\" to the subvariable).");
                    }
                    return _sortByBuiltIn.Sort(_seq, subvars);
                }

                public object Exec(IList<string> arguments)
                {
                    throw new NotSupportedException();
                }
            }
        }

        internal class SeqContainsBuiltIn : BuiltIn
        {
            protected override ITemplateModel _getAsTemplateModel(Environment env)
            {
                ITemplateModel model = Target.GetAsTemplateModel(env);
                if (model is ITemplateSequenceModel)
                {
                    return new BuiltInMethodForSequence((ITemplateSequenceModel) model, env);
                }
                if (model is ITemplateCollectionModel)
                {
                    return new BuiltInMethodForCollection((ITemplateCollectionModel) model, env);
                }
                throw InvalidTypeException(model, Target, env, "sequence or collection");
            }

            private class BuiltInMethodForSequence : ITemplateMethodModelEx
            {
                private readonly ITemplateSequenceModel _seq;
                private readonly Environment _env;

                internal BuiltInMethodForSequence(ITemplateSequenceModel seq, Environment env)
                {
                    _seq = seq;
                    _env = env;
                }

                public object Exec(IList<ITemplateModel> args)
                {
                    if (args.Count != 1)
                    {
                        throw new TemplateModelException("?seq_contains(...) expects one argument.");
                    }
                    ITemplateModel arg = args[0];
                    int size = _seq.Size();
                    for (int i = 0; i < size; i++)
                    {
                        if (ModelsEqual(_seq.Get(i), arg, _env))
                        {
                            return TemplateBooleanModel.True;
                        }
                    }
                    return TemplateBooleanModel.False;
                }

                public object Exec(IList<string> arguments)
                {
                    throw new NotSupportedException();
                }
            }

            private class BuiltInMethodForCollection : ITemplateMethodModelEx
            {
                private readonly ITemplateCollectionModel _coll;
                private readonly Environment _env;

                internal BuiltInMethodForCollection(ITemplateCollectionModel coll, Environment env)
                {
                    _coll = coll;
                    _env = env;
                }

                public object Exec(IList<ITemplateModel> args)
                {
                    if (args.Count != 1)
                    {
                        throw new TemplateModelException("?seq_contains(...) expects one argument.");
                    }
                    ITemplateModel arg = args[0];
                    ITemplateModelIterator it = _coll.Iterator();
                    while (it.HasNext())
                    {
                        if (ModelsEqual(it.Next(), arg, _env))
                        {
                            return TemplateBooleanModel.True;
                        }
                    }
                    return TemplateBooleanModel.False;
                }

                public object Exec(IList<string> arguments)
                {
                    throw new NotSupportedException();
                }
            }
        }

        internal class SeqIndexOfBuiltIn : BuiltIn
        {
            private readonly int _dir;

            public SeqIndexOfBuiltIn(int dir)
            {
                _dir = dir;
            }

            protected override ITemplateModel _getAsTemplateModel(Environment env)
            {
                ITemplateModel model = Target.GetAsTemplateModel(env);
                if (!(model is ITemplateSequenceModel))
                {
                    throw InvalidTypeException(model, Target, env, "sequence");
                }
                return new BuiltInMethod(this, (ITemplateSequenceModel) model, env);
            }

            private class BuiltInMethod : ITemplateMethodModelEx
            {
                private readonly SeqIndexOfBuiltIn _seqIndexOfBuiltIn;
                private readonly ITemplateSequenceModel _seq;
                private readonly Environment _env;

                internal BuiltInMethod(SeqIndexOfBuiltIn seqIndexOfBuiltIn, ITemplateSequenceModel seq, Environment env)
                {
                    _seqIndexOfBuiltIn = seqIndexOfBuiltIn;
                    _seq = seq;
                    _env = env;
                }

                public object Exec(IList<ITemplateModel> args)
                {
                    int argcnt = args.Count;
                    if (argcnt != 1 && argcnt != 2)
                    {
                        throw new TemplateModelException(
                            GetBuiltinTemplate() + " expects 1 or 2 arguments.");
                    }

                    int startIndex;
                    int seqSize = _seq.Size();
                    ITemplateModel arg = args[0];
                    if (argcnt > 1)
                    {
                        ITemplateModel obj = args[1];
                        if (!(obj is ITemplateNumberModel))
                        {
                            throw new TemplateModelException(
                                GetBuiltinTemplate()
                                + "expects a number as its second argument.");
                        }
                        startIndex = ((ITemplateNumberModel)obj).GetAsNumber().IntValue();
                        if (_seqIndexOfBuiltIn._dir == 1)
                        {
                            if (startIndex >= seqSize)
                            {
                                return Constants.MinusOne;
                            }
                            if (startIndex < 0)
                            {
                                startIndex = 0;
                            }
                        }
                        else
                        {
                            if (startIndex >= seqSize)
                            {
                                startIndex = seqSize - 1;
                            }
                            if (startIndex < 0)
                            {
                                return Constants.MinusOne;
                            }
                        }
                    }
                    else
                    {
                        if (_seqIndexOfBuiltIn._dir == 1)
                        {
                            startIndex = 0;
                        }
                        else
                        {
                            startIndex = seqSize - 1;
                        }
                    }

                    if (_seqIndexOfBuiltIn._dir == 1)
                    {
                        for (int i = startIndex; i < seqSize; i++)
                        {
                            if (ModelsEqual(_seq.Get(i), arg, _env))
                            {
                                return new SimpleNumber(i);
                            }
                        }
                    }
                    else
                    {
                        for (int i = startIndex; i >= 0; i--)
                        {
                            if (ModelsEqual(_seq.Get(i), arg, _env))
                            {
                                return new SimpleNumber(i);
                            }
                        }
                    }
                    return Constants.MinusOne;
                }

                public object Exec(IList<string> arguments)
                {
                    throw new NotSupportedException();
                }

                private string GetBuiltinTemplate()
                {
                    return _seqIndexOfBuiltIn._dir == 1
                        ? "?seq_indexOf(...)"
                        : "?seq_lastIndexOf(...)";
                }
            }
        }

        internal class ChunkBuiltIn : SequenceBuiltIn
        {
            protected override ITemplateModel CalculateResult(ITemplateSequenceModel tsm)
            {
                return new BuiltInMethod(tsm);
            }

            private class BuiltInMethod : ITemplateMethodModelEx
            {
                private readonly ITemplateSequenceModel _tsm;

                internal BuiltInMethod(ITemplateSequenceModel tsm)
                {
                    _tsm = tsm;
                }

                public object Exec(IList<ITemplateModel> args)
                {
                    int numArgs = args.Count;
                    if (numArgs != 1 && numArgs != 2)
                    {
                        throw new TemplateModelException(
                            "?chunk(...) expects 1 or 2 arguments.");
                    }

                    ITemplateModel chunkSize = args[0];
                    if (!(chunkSize is ITemplateNumberModel))
                    {
                        throw new TemplateModelException(
                            "?chunk(...) expects a number as "
                            + "its 1st argument.");
                    }

                    return new ChunkedSequence(
                        _tsm,
                        ((ITemplateNumberModel)chunkSize).GetAsNumber().IntValue(),
                        numArgs > 1 ? args[1] : null);
                }

                public object Exec(IList<string> arguments)
                {
                    throw new NotSupportedException();
                }
            }

            private class ChunkedSequence : ITemplateSequenceModel
            {
                private readonly ITemplateSequenceModel _wrappedTsm;
                private readonly int _chunkSize;
                private readonly ITemplateModel _fillerItem;
                private readonly int _numberOfChunks;

                internal ChunkedSequence(
                    ITemplateSequenceModel wrappedTsm, int chunkSize, ITemplateModel fillerItem)
                {
                    if (chunkSize < 1)
                    {
                        throw new TemplateModelException(
                            "The 1st argument to ?chunk(...) must be at least 1.");
                    }
                    _wrappedTsm = wrappedTsm;
                    _chunkSize = chunkSize;
                    _fillerItem = fillerItem;
                    _numberOfChunks = (wrappedTsm.Size() + chunkSize - 1)/chunkSize;
                }

                public ITemplateModel Get(int chunkIndex)
                {
                    if (chunkIndex >= _numberOfChunks)
                    {
                        return null;
                    }
                    return new ChunkedSequenceModel(this, chunkIndex);
                }

                public int Size()
                {
                    return _numberOfChunks;
                }

                private class ChunkedSequenceModel : ITemplateSequenceModel
                {
                    private readonly ChunkedSequence _chunkedSequence;
                    private readonly int _chunkIndex;
                    private readonly int _baseIndex;

                    public ChunkedSequenceModel(ChunkedSequence chunkedSequence, int chunkIndex)
                    {
                        _chunkedSequence = chunkedSequence;
                        _chunkIndex = chunkIndex;
                        _baseIndex = chunkIndex * _chunkedSequence._chunkSize;
                    }

                    public ITemplateModel Get(int relIndex)
                    {
                        int absIndex = _baseIndex + relIndex;
                        if (absIndex < _chunkedSequence._wrappedTsm.Size())
                        {
                            return _chunkedSequence._wrappedTsm.Get(absIndex);
                        }
                        else
                        {
                            return absIndex < _chunkedSequence._numberOfChunks * _chunkedSequence._chunkSize
                                ? _chunkedSequence._fillerItem
                                : null;
                        }
                    }

                    public int Size()
                    {
                        return (_chunkedSequence._fillerItem != null ||
                                _chunkIndex + 1 < _chunkedSequence._numberOfChunks)
                                   ? _chunkedSequence._chunkSize
                                   : _chunkedSequence._wrappedTsm.Size() - _baseIndex;
                    }
                }
            }
        }

        /*
         * WARNING! This algorithm is duplication of ComparisonExpression.isTrue(...).
         * Thus, if you update this method, then you have to update that too!
         */

        private static bool ModelsEqual(ITemplateModel model1, ITemplateModel model2,
                                        Environment env)
        {
            if (env.IsClassicCompatible())
            {
                if (model1 == null)
                {
                    model1 = TemplateScalarModel.EmptyString;
                }
                if (model2 == null)
                {
                    model2 = TemplateScalarModel.EmptyString;
                }
            }

            int comp = -1;
            if (model1 is ITemplateNumberModel && model2 is ITemplateNumberModel)
            {
                Number first = ((ITemplateNumberModel) model1).GetAsNumber();
                Number second = ((ITemplateNumberModel) model2).GetAsNumber();
                ArithmeticEngine ae = env.GetArithmeticEngine();
                try
                {
                    comp = ae.CompareNumbers(first, second);
                }
                catch (TemplateException ex)
                {
                    throw new TemplateModelException(ex);
                }
            }
            else if (model1 is ITemplateDateModel && model2 is ITemplateDateModel)
            {
                var ltdm = (ITemplateDateModel) model1;
                var rtdm = (ITemplateDateModel) model2;
                int ltype = ltdm.GetDateType();
                int rtype = rtdm.GetDateType();
                if (ltype != rtype)
                {
                    throw new TemplateModelException(
                        "Can not compare dates of different type. Left date is of "
                        + TemplateDateModel.TypeNames[ltype]
                        + " type, right date is of "
                        + TemplateDateModel.TypeNames[rtype] + " type.");
                }
                if (ltype == TemplateDateModel.Unknown)
                {
                    throw new TemplateModelException(
                        "Left date is of UNKNOWN type, and can not be compared.");
                }
                if (rtype == TemplateDateModel.Unknown)
                {
                    throw new TemplateModelException(
                        "Right date is of UNKNOWN type, and can not be compared.");
                }
                DateTimeOffset first = ltdm.GetAsDate();
                DateTimeOffset second = rtdm.GetAsDate();
                comp = first.CompareTo(second);
            }
            else if (model1 is ITemplateScalarModel && model2 is ITemplateScalarModel)
            {
                string first = ((ITemplateScalarModel) model1).GetAsString();
                string second = ((ITemplateScalarModel) model2).GetAsString();
                comp = string.Compare(first, second, env.GetLocale(), CompareOptions.None);
            }
            else if (model1 is ITemplateBooleanModel && model2 is ITemplateBooleanModel)
            {
                bool first = ((ITemplateBooleanModel) model1).GetAsBoolean();
                bool second = ((ITemplateBooleanModel) model2).GetAsBoolean();
                comp = (first ? 1 : 0) - (second ? 1 : 0);
            }

            return (comp == 0);
        }
    }
}